#include <string>
#include <sstream>
#include <fstream>
#include <algorithm>
#include <functional>
#include <iostream>	// just for cout
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>

#include "database.h"
#include "../protocol.h"

using namespace std;
using namespace protocol;

namespace database {

	Database::Database() {
		init_db();
	}

	Database::~Database() {
#ifdef IN_DISK
                cout << "Database in-disk version\n";
#else
                cout << "Database in-memory version\n";
#endif
#ifndef IN_DISK
                // SYNCRONIZE DATABASE ON DISK
                stringstream* ss;
                ss = new stringstream;
                *ss << "rm -R -f '" << DATA_PATH << "'";
                system(ss->str().c_str());
                delete ss;
                ss = new stringstream;
                *ss << "mkdir '" << DATA_PATH << "'";
                system(ss->str().c_str());
                delete ss;

                for(ng_list::iterator it = ngs.begin(); it != ngs.end(); ++it)
                {
                        ss = new stringstream;
                        *ss << "mkdir '" << DATA_PATH << it->first.id << " " << it->first.name << "'";
                        system(ss->str().c_str());
                        list<Article> articles = listArticles(it->first.id);
                        delete ss;
                        for(list<Article>::iterator a_it = articles.begin(); a_it != articles.end(); ++a_it)
                        {
                                string str = a_it->date + '\n' + a_it->author + '\n' + a_it->title + '\n' + a_it->text;
                                ss = new stringstream;
                                *ss << DATA_PATH << it->first.id << " " << it->first.name << "/" << a_it->id;
                                ofstream out(ss->str().c_str());
                                out.write(str.c_str(), str.size());
                                delete ss;
                        }
                }
#endif
        }

	// Initializes the database: read all persistent data to working memory
	void Database::init_db() {
		cout << "Reading directories and files from " << DATA_PATH << endl;

		DIR *pdir;
		DIR *art_pdir;
		int ng_id;
		string ng_name, tmp, tmp2;
		list<Article> art_list;

		struct dirent *pent;

		pdir=opendir(DATA_PATH);

		while ((pent=readdir(pdir))){
			if(strcmp(pent->d_name, ".") && strcmp(pent->d_name, "..") && strcmp(pent->d_name, ".svn")) {
				tmp = pent->d_name;
				istringstream iss(tmp);
				iss >> tmp2;
				ng_id = atoi(tmp2.c_str());
				iss.ignore(1);
				getline(iss, ng_name);
				Newsgroup ng = {ng_id, ng_name};
				ngs.push_back(make_pair(ng, art_list));
				//cout << "Added newsgroup, ID: "<< ng_id << " name: " << ng_name << endl;
			}
			else
				continue;

			ng_list::iterator ng_it = findNewsgroup(ng_id);

			string str = DATA_PATH;
			str += pent->d_name;
			str += '/';
			art_pdir=opendir(str.c_str());
			while ((pent=readdir(art_pdir))) {
				if(strcmp(pent->d_name, ".") && strcmp(pent->d_name, "..") && strcmp(pent->d_name, ".svn")) {
					Article art;
					art.id = atoi(pent->d_name);
					string art_path = DATA_PATH + tmp + '/';
					art_path += pent->d_name;
					//cout << "reading article from path " << art_path << endl;
					ifstream ifs(art_path.c_str());
					//if(!ifs.is_open()) cout << "Unable to read article" << endl;
					getline(ifs, art.date);
					getline(ifs, art.author);
					getline(ifs, art.title);
					while(getline(ifs, tmp2))
						art.text += tmp2;
					ng_it->second.push_back(art);
					//cout << " > Added article: " << pent->d_name << endl;
					ifs.close();
				}
			}

		}

		closedir(pdir);
	}

	// Adds a newsgroup, both in memory and on disk
	bool Database::addNewsgroup(string name) {
		if(findNewsgroup(name) != ngs.end())
			throw static_cast<unsigned char>(Protocol::ERR_NG_ALREADY_EXISTS);

		//in working mem
		Newsgroup ng = {largestNewsgroupID() + 1, name};
		list<Article> art;
		ngs.push_back(make_pair(ng, art));

		//in disk
#ifdef IN_DISK                
		stringstream ss;
		ss << "mkdir '" << DATA_PATH << ng.id << " " << name << "'";
		if(system(ss.str().c_str())) return false;
#endif
		return true;
	}

	// Deletes a newsgroup, both in memory and on disk
	bool Database::deleteNewsgroup(int id) {
		ng_list::iterator it = findNewsgroup(id);

		if(it == ngs.end() || it->first.name == "")
			throw static_cast<unsigned char>(Protocol::ERR_NG_DOES_NOT_EXIST);

		// on disk
#ifdef IN_DISK                
		stringstream ss;
		ss << "rm -R -f '" << DATA_PATH << it->first.id << " " << it->first.name << "'";
		if(system(ss.str().c_str())) return false;
#endif

		// in working mem
		ngs.erase(it);

		return true;
	}

	// Retrieves a list of newsgroups from working memory
	list<Newsgroup> Database::listNewsgroups() {
		list<Newsgroup> ret;

		for(ng_list::iterator it = ngs.begin(); it != ngs.end(); ++it)
			ret.push_back(it->first);

		ret.sort();

		return ret;
	}

	// Adds an article, both in memory and on disk
	bool Database::addArticle(int ng_id, string date, string author, string title, string text) {
		ng_list::iterator it = findNewsgroup(ng_id);

		if(it == ngs.end() || it->first.name == "")
			throw static_cast<unsigned char>(Protocol::ERR_NG_DOES_NOT_EXIST);

		Article art = {largestArticleID(it->second) + 1, date, author, title, text};

		// in working mem
		it->second.push_back(art);

		// on disk
		string str = date + '\n' + author + '\n' + title + '\n' + text;
#ifdef IN_DISK                
		stringstream ss;
		ss << DATA_PATH << it->first.id << " " << it->first.name << "/" << art.id;
		ofstream out(ss.str().c_str());
		out.write(str.c_str(), str.size());
		if(out.fail()) return false;
#endif
		cout << "Added article: " << str << endl;
		return true;
	}

	// Deletes an article, both in memory and on disk
	bool Database::deleteArticle(int ng_id, int art_id) {
		ng_list::iterator ng_it = findNewsgroup(ng_id);

		if(ng_it == ngs.end() || ng_it->first.name == "")
			throw static_cast<unsigned char>(Protocol::ERR_NG_DOES_NOT_EXIST);

		// on disk
#ifdef IN_DISK                
		stringstream ss;
		ss << "rm -f '" << DATA_PATH << ng_it->first.id << " " << ng_it->first.name << '/' << art_id << "'";
		if(system(ss.str().c_str())) return false;
#endif

		// in working mem
		art_list::iterator art_it = ng_it->second.begin();
		for(; art_it != ng_it->second.end(); ++art_it) {
			if(art_it->id == art_id) {
				ng_it->second.erase(art_it);
				return true;
			}
		}
		if(art_it == ng_it->second.end())
			throw static_cast<unsigned char>(Protocol::ERR_ART_DOES_NOT_EXIST);

		return true;
	}

	// Returns a list of the articles in the specified newsgroup
	list<Article> Database::listArticles(int ng_id) {
		list<Article> ret;

		ng_list::iterator ng_it = findNewsgroup(ng_id);

		if(ng_it == ngs.end())
			throw static_cast<unsigned char>(Protocol::ERR_NG_DOES_NOT_EXIST);

		art_list::iterator art_it = ng_it->second.begin();
		for(; art_it != ng_it->second.end(); ++art_it) {
			ret.push_back(*art_it);
		}

		ret.sort();

		return ret;
	}

	// Returns an iterator, pointing at the specified article in working memory
	Article Database::getArticle(int ng_id, int art_id) {
		ng_list::iterator ng_it = findNewsgroup(ng_id);

		if(ng_it == ngs.end() || ng_it->first.name == "")
			throw static_cast<unsigned char>(Protocol::ERR_NG_DOES_NOT_EXIST);

		art_list::iterator art_it = ng_it->second.begin();
		for(; art_it->id != art_id && art_it != ng_it->second.end(); ++art_it){}

		if(art_it == ng_it->second.end())
			throw static_cast<unsigned char>(Protocol::ERR_ART_DOES_NOT_EXIST);

		return (*art_it);
	}

	// Internal function for finding the location of a newsgroup, in working memory
	ng_list::iterator Database::findNewsgroup(int id) {
		ng_list::iterator it = ngs.begin();
		for(; it->first.id != id && it != ngs.end(); ++it){}
		return it;
	}

	ng_list::iterator Database::findNewsgroup(string name) {
		ng_list::iterator it = ngs.begin();
		for(; it->first.name != name && it != ngs.end(); ++it){} 
		return it;
	}

	int Database::largestNewsgroupID() {
		int max = 0;
		for(ng_list::iterator it = ngs.begin(); it != ngs.end(); ++it)
			if(it->first.id > max) max = it->first.id;
		return max;
	}

	int Database::largestArticleID(art_list& al) {
		int max = 0;
		for(art_list::iterator it = al.begin(); it != al.end(); ++it)
			if(it->id > max) max = it->id;
		return max;
	}
}
